package cn.yj.wx.pay.service.handler;

import cn.yj.common.emums.PayType;
import cn.yj.common.entity.PayOrder;
import cn.yj.common.entity.PayRefundOrder;
import cn.yj.common.entity.RefundQueryParams;
import cn.yj.common.exception.PayException;
import cn.yj.common.interfaces.AbstractPay;
import cn.yj.common.monitor.WechatOrderMonitor;
import cn.yj.commons.utils.BigDecimalUtils;
import cn.yj.commons.utils.DateTimeUtils;
import cn.yj.commons.utils.StringUtils;
import cn.yj.tools.exception.ServiceException;
import cn.yj.wx.pay.config.PayConfig;
import cn.yj.wx.pay.emums.ErrorCode;
import cn.yj.wx.pay.emums.RefundWechatStatus;
import cn.yj.wx.pay.enrity.Amount;
import cn.yj.wx.pay.enrity.BaseRequest;
import cn.yj.wx.pay.enrity.PayOrderResult;
import cn.yj.wx.pay.enrity.refund.RefundRequest;
import cn.yj.wx.pay.enrity.refund.WechatRefundResult;
import cn.yj.wx.pay.utils.WxPayUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.text.ParseException;
import java.util.Date;
import java.util.Map;

/**
 * @author 永健
 * @since 2021-09-22 16:48
 */
public abstract class AbstractWxPay<T extends PayOrderResult, Q extends BaseRequest> extends AbstractPay<Q, T> implements IWxPay<T, Q>, EnvironmentAware {

    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());

    private static PrivateKey privateKey;

    private static AutoUpdateCertificatesVerifier verifier;

    private static final String ORDER_QUERY_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no";
    private static final String REFUND_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds";
    private static final String REFUND_STATUS_URL = "https://api.mch.weixin.qq.com/v3/refund/domestic/refunds/";

    @Override
    public void setEnvironment(Environment environment) {

    }

    @Resource
    protected void setPayOrderMonitor(WechatOrderMonitor payOrderMonitor) {
        super.setIPayOrderMonitor(payOrderMonitor);
    }

    protected static PrivateKey getPrivateKey() {
        return privateKey;
    }

    PayConfig config() {
        return getApplicationContext().getBean(PayConfig.class);
    }


    /**
     * 初始化证书
     */
    @PostConstruct
    private void init() throws IOException {
        if (StringUtils.isNull(verifier)) {
            // 加载商户私钥（privateKey：私钥字符串）
            privateKey = PemUtil.loadPrivateKey(config().getKetStream());

            // 加载平台证书（mchId：商户号,mchSerialNo：商户证书序列号,apiV3Key：V3密钥）
            verifier = new AutoUpdateCertificatesVerifier(new WechatPay2Credentials(config().getMchId(),
                    new PrivateKeySigner(config().getMchSerialNo(), privateKey)), config().getApiV3Key().getBytes(
                            "utf-8"));

            if (StringUtils.isBlank(config().getNotifyUrl())) {
                LOGGER.error("微信支付结果回调通知URL，未配置:pay.wechat.notice-url");
            }
        }
    }


    /**
     * 下单接口
     *
     * @param payRequest
     *
     * @throws IOException
     */
    protected T doUnderOrder(String host, Q payRequest) throws PayException {
        if (StringUtils.isBlank(host)) {
            throw new ServiceException("微信支付下单地址是空的:host is null");
        }
        // 初始化httpClient
        HttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(config().getMchId(),
                config().getMchSerialNo(), privateKey).withValidator(new WechatPay2Validator(verifier)).build();
        org.apache.http.client.methods.HttpPost httpHost = new org.apache.http.client.methods.HttpPost(host);
        httpHost.setHeader("Accept", "application/json");
        httpHost.setHeader("Content-type", "application/json; charset=utf-8");

        // 是否使用默认的ID
        String appid = payRequest.getAppid();
        String mchid = payRequest.getMchid();
        if (StringUtils.isBlank(appid) || StringUtils.isBlank(mchid)) {
            payRequest.setAppid(config().getAppId());
            payRequest.setMchid(config().getMchId());
        }

        // 金额校验
        Amount amount = payRequest.getAmount();
        if (StringUtils.isNull(amount)) {
            throw new PayException("请输订单金额信息:total:" + amount);
        }

        if (BigDecimalUtils.isLeftLERight(amount.getTotal(), BigDecimal.ZERO)) {
            throw new PayException("订单金额要大于0:total");
        }

        // 支付结果回调通知
        if (StringUtils.isBlank(payRequest.getNotify_url())) {
            payRequest.setNotify_url(config().getNotifyUrl());
        }

        // 自定义入参校验
        this.checkPayRequestParams(payRequest);

        // 检查时间默认为最大不能超过15天
        if (StringUtils.isNotBlank(payRequest.getTime_expire())) {
            String time_expire = payRequest.getTime_expire();
            try {
                Date date = DateTimeUtils.format(time_expire);
                long time = date.getTime();

                if (time > System.currentTimeMillis() + (15 * 24 * 60 * 60 * 1000) || time<System.currentTimeMillis() + (5 * 60 * 1000)) {
                    throw new ServiceException("支付过期时间最5m-15d:" + time_expire);
                }
                payRequest.setTime_expire(WxPayUtils.parseDate(date));
            } catch (ParseException e) {
                e.printStackTrace();
                throw new ServiceException("支付过期时间异常:" + time_expire);
            }
        }


        // 设置请求参数
        StringEntity stringEntity = new StringEntity(JSON.toJSONString(payRequest), Charset.defaultCharset());
        httpHost.setEntity(stringEntity);
        String result = null;
        try {
            HttpResponse response = httpClient.execute(httpHost);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, Charset.defaultCharset());
        } catch (IOException e) {
            e.printStackTrace();
            throw new PayException("下单异常");
        }
        LOGGER.info("result:{}", result);
        // 结果转换
        Class<T> tClass;
        try {
            tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        } catch (Exception e) {
            LOGGER.error("未传入范型 T {}", e);
            tClass = (Class<T>) PayOrderResult.class;
        }

        T t = JSON.parseObject(result, tClass);
        String code = t.getCode();
        if (StringUtils.isNotBlank(code)) {
            ErrorCode errorCode = ErrorCode.valueOf(code);
            LOGGER.error("{},{}", errorCode.getMsg(), errorCode.getResolvent());
            throw new PayException(t.getMessage());
        }
        Gson gson = new Gson();
        Map<String, String> map = gson.fromJson(result, Map.class);
        String prepay_id = map.get("prepay_id");
        String h5_url = map.get("h5_url");
        String code_url = map.get("code_url");
        t.setResult(StringUtils.isNotBlank(prepay_id)? prepay_id : (StringUtils.isNotBlank(h5_url)? h5_url : code_url));
        try {
            this.requestPayAfter(t);
        } catch (Exception e) {
            e.printStackTrace();
            throw new PayException("微信支付v3签名失败");
        }
        super.addPayOrderMonitor(10000, payRequest.getOut_trade_no(), this);
        return t;
    }


    /**
     * 订单查询，查询支付状态
     *
     * @param outTradeNo
     */
    @Override
    public PayOrder queryOrder(String outTradeNo) {
        // 初始化httpClient
        HttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(config().getMchId(),
                config().getMchSerialNo(), privateKey).withValidator(new WechatPay2Validator(verifier)).build();

        HttpGet httpHost =
                new HttpGet(ORDER_QUERY_URL.concat("/").concat(outTradeNo).concat("?mchid=").concat(config().getMchId()));
        httpHost.setHeader("Accept", "application/json");
        httpHost.setHeader("Content-type", "application/json; charset=utf-8");

        Gson gson = new Gson();
        String result;
        try {
            HttpResponse response = httpClient.execute(httpHost);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, Charset.defaultCharset());
        } catch (IOException e) {
            e.printStackTrace();
            throw new PayException("查询订单异常");
        }
        LOGGER.info("result:{}", result);
        JSONObject jsonObject = gson.fromJson(result, JSONObject.class);
        String trade_state = jsonObject.getString("trade_state");
        PayOrder payOrder = new PayOrder(PayType.WECHAT, outTradeNo);
        payOrder.setTradeStatus(trade_state);
        payOrder.setSuccess("SUCCESS".equals(trade_state));
        return payOrder;
    }


    /**
     * 退款接口
     *
     * @param refundRequest
     *
     * @throws IOException
     */
    public WechatRefundResult refund(RefundRequest refundRequest) {

        RefundRequest.Amount amount = refundRequest.getAmount();
        int refundAmount = amount.getRefund();
        int sourceAmount = amount.getTotal();

        String refund_no = refundRequest.getOut_refund_no();

        String out_trade_no = refundRequest.getOut_trade_no();

        if (StringUtils.isBlank(refundRequest.getNotify_url())) {
            refundRequest.setNotify_url(config().getRefundUrl());
        }

        if (StringUtils.isNull(amount)) {
            throw new ServiceException("请输入退款金额");
        }

        if (StringUtils.isBlank(out_trade_no)) {
            throw new ServiceException("缺少商户订单号");
        }

        if (StringUtils.isBlank(refund_no)) {
            throw new ServiceException("缺少退款单号");
        }

        if (refundAmount <= 0) {
            throw new ServiceException("退款金额有误");
        }

        if (sourceAmount < refundAmount) {
            throw new ServiceException("退款金额不能大于原订单金额");
        }


        // 初始化httpClient
        HttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(config().getMchId(),
                config().getMchSerialNo(), privateKey).withValidator(new WechatPay2Validator(verifier)).build();
        HttpPost httpHost = new HttpPost(REFUND_URL);
        httpHost.setHeader("Accept", "application/json");
        httpHost.setHeader("Content-type", "application/json; charset=utf-8");

        Gson gson = new Gson();

        // 设置请求参数
        StringEntity stringEntity = new StringEntity(gson.toJson(refundRequest), Charset.defaultCharset());
        httpHost.setEntity(stringEntity);
        String result;
        try {
            HttpResponse response = httpClient.execute(httpHost);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, Charset.defaultCharset());
            LOGGER.info("result:{}", result);
        } catch (IOException e) {
            e.printStackTrace();
            throw new PayException(e.getMessage());
        }

        // 一般是请求参数有误：比如缺少或者多余的。或者金额不对
        JSONObject sourceObj = gson.fromJson(result, JSONObject.class);
        String message = sourceObj.getString("message");
        if (StringUtils.isNotBlank(message)) {
            throw new PayException(message);
        }

        WechatRefundResult refundResult = gson.fromJson(result, WechatRefundResult.class);
        String code = refundResult.getStatus();
        if (!RefundWechatStatus.SUCCESS.name().equals(code)) {
            RefundWechatStatus[] values = RefundWechatStatus.values();
            for (RefundWechatStatus status : values) {
                if (status.name().equals(code)) {
                    refundResult.setStatusMsg(status.getMsg());
                    break;
                }
            }
            if (RefundWechatStatus.PROCESSING.name().equals(code)) {
                super.addRefundOrderMonitor(new RefundQueryParams(refund_no), this);
            }
        } else {
            super.addRefundOrderMonitor(new RefundQueryParams(refund_no), this);
        }
        return refundResult;
    }

    @Override
    public PayRefundOrder queryRefundOrder(RefundQueryParams params) throws PayException {
        HttpClient httpClient = WechatPayHttpClientBuilder.create().withMerchant(config().getMchId(),
                config().getMchSerialNo(), privateKey).withValidator(new WechatPay2Validator(verifier)).build();
        HttpGet httpGet = new HttpGet(REFUND_STATUS_URL.concat(params.getOutRefundNo()));
        httpGet.setHeader("Accept", "application/json");
        httpGet.setHeader("Content-type", "application/json; charset=utf-8");
        Gson gson = new Gson();
        String result;
        try {
            HttpResponse response = httpClient.execute(httpGet);
            HttpEntity entity = response.getEntity();
            result = EntityUtils.toString(entity, Charset.defaultCharset());
        } catch (IOException e) {
            e.printStackTrace();
            throw new PayException("查询订单异常");
        }
        LOGGER.info("result:{}", result);
        // 一般是请求参数有误：比如缺少或者多余的。或者金额不对
        JSONObject sourceObj = gson.fromJson(result, JSONObject.class);
        String message = sourceObj.getString("message");
        if (StringUtils.isNotBlank(message)) {
            throw new PayException(message);
        }

        PayRefundOrder payRefundOrder = new PayRefundOrder();
        payRefundOrder.setOutRefundNo(params.getOutRefundNo());
        payRefundOrder.setOutTradeNo(params.getOutTradeNo());
        payRefundOrder.setPayType(PayType.WECHAT);
        double refundAmount = sourceObj.getJSONObject("amount").getDouble("refund");
        payRefundOrder.setRefundAmount(BigDecimalUtils.format(refundAmount / 100));
        String status = sourceObj.getString("status");
        payRefundOrder.setStatus(status);
        boolean success = RefundWechatStatus.SUCCESS.name().equals(status);
        if (success) {
            payRefundOrder.setSuccess(true);
        }
        return payRefundOrder;
    }

    /**
     * 自定义参数校验
     *
     * @param payRequest
     */
    protected void checkPayRequestParams(Q payRequest) {
    }

    /**
     * 对结果进行更改
     *
     * @param payResult
     */
    protected void requestPayAfter(T payResult) throws NoSuchAlgorithmException, SignatureException,
            InvalidKeyException {
    }


}
